package com.quick.develop.flowable.service.process.impl;

import cn.hutool.core.collection.CollUtil;
import com.quick.develop.flowable.constant.BpmConstants;
import com.quick.develop.flowable.domain.BpmBaseDO;
import com.quick.develop.flowable.domain.vo.instance.BpmProcessInstanceCreateReqVO;
import com.quick.develop.flowable.enums.BpmExecuteCmd;
import com.quick.develop.framework.common.page.PageResult;
import com.quick.develop.framework.utils.PageUtils;
import com.quick.develop.framework.utils.StringUtils;
import com.quick.develop.framework.utils.collection.CollectionUtils;
import com.quick.develop.framework.security.utils.SecurityUtils;
import com.quick.develop.flowable.domain.instance.BpmProcessInstanceMyPageReqVO;
import com.quick.develop.flowable.domain.instance.BpmProcessInstancePageItemRespVO;
import com.quick.develop.flowable.domain.instance.BpmProcessInstanceRespVO;
import com.quick.develop.flowable.framework.convert.instance.BpmProcessInstanceConvert;
import com.quick.develop.flowable.framework.enums.istance.BpmProcessInstanceStatusEnum;
import com.quick.develop.flowable.framework.factory.FlowServiceFactory;
import com.quick.develop.flowable.utils.FlowableUtil;
import com.quick.develop.flowable.mapper.BpmProcessInstanceMapper;
import com.quick.develop.flowable.service.definition.IBpmProcessDefinitionService;
import com.quick.develop.flowable.service.process.IBpmProcessInstanceService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.util.IoUtil;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

import static com.quick.develop.flowable.enums.BpmErrorCodeConstants.*;
import static com.quick.develop.framework.common.utils.ServiceExceptionUtils.exception;

/**
 * @description:  流程实例 Service 实现类
 * @author junyuan.liu
 * @date 2022/4/22 11:12
 */
@Service
@Slf4j
@Validated
public class BpmProcessInstanceServiceImpl extends FlowServiceFactory implements IBpmProcessInstanceService {

    @Resource
    private IBpmProcessDefinitionService processDefinitionService;
    
    @Resource
    private BpmProcessInstanceMapper processInstanceMapper;

    @Override
    public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(BpmProcessInstanceMyPageReqVO pageReqVO) {
        String userCode = SecurityUtils.getUserCode();
        boolean admin = SecurityUtils.isAdmin();
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .orderByProcessInstanceStartTime()
                .desc();
        Optional<Boolean> optionalLog= Optional.ofNullable(pageReqVO.getIsLog());
        if (!admin && !optionalLog.isPresent() ) {
            historicProcessInstanceQuery.excludeSubprocesses(true).startedBy(userCode);
        }
        if (Objects.equals(pageReqVO.getStatus(), BpmProcessInstanceStatusEnum.RUNNING.getStatus()) ) {
            historicProcessInstanceQuery.unfinished();
        }
        if (Objects.equals(pageReqVO.getStatus(), BpmProcessInstanceStatusEnum.FINISH.getStatus())){
            historicProcessInstanceQuery.finished();
        }
        
        if (StringUtils.isNotEmpty(pageReqVO.getBusinessKey())) {
            historicProcessInstanceQuery.processInstanceBusinessKey(pageReqVO.getBusinessKey());
        }

        if (StringUtils.isNotEmpty(pageReqVO.getName())) {
            historicProcessInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");
        }
        if (StringUtils.isNotEmpty(pageReqVO.getProcessDefinitionName())) {
            historicProcessInstanceQuery.processDefinitionName(pageReqVO.getProcessDefinitionName());
        }
        if (StringUtils.isNotEmpty(pageReqVO.getProcessInstanceId())) {
            historicProcessInstanceQuery.processInstanceId(pageReqVO.getProcessInstanceId());
        }
        if (StringUtils.isNotEmpty(pageReqVO.getProcessDefinitionKey())) {
            historicProcessInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey());
        }
        Optional.ofNullable(pageReqVO.getBeginCreateTime()).ifPresent(historicProcessInstanceQuery::startedAfter);
        Optional.ofNullable(pageReqVO.getEndCreateTime()).ifPresent(historicProcessInstanceQuery::startedBefore);

        List<HistoricProcessInstance> hisProInsts = historicProcessInstanceQuery
                .includeProcessVariables()
                .listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());
        /*Set<String> defIds = convertSet(hisProInsts, HistoricProcessInstance::getProcessDefinitionId);
        Map<String, ProcessDefinition> pds = processDefinitionService.getProcessDefinitionMap(defIds);
        // 获得流程 Task Map
        List<String> processInstanceIds = convertList(hisProInsts, HistoricProcessInstance::getId);
        Map<String, List<Task>> taskMap = bpmTaskService.getTaskMapByProcessInstanceIds(processInstanceIds);*/

        return new PageResult<>(BpmProcessInstanceConvert.INSTANCE.convertPage(hisProInsts) , historicProcessInstanceQuery.count());
    }

    @Override
    public long getMyProcessCount() {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        boolean admin = SecurityUtils.isAdmin();
        if (!admin) {
            historicProcessInstanceQuery.startedBy(SecurityUtils.getUserCode()).excludeSubprocesses(true);
        }
        
        return historicProcessInstanceQuery.count();
    }


    @Override
    public ProcessInstance getProcessInstance(String id) {
        return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
    }

    @Override
    public List<ProcessInstance> getProcessInstanceList(Set<String> ids) {
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
    }

    @Override
    public HistoricProcessInstance getHistoricProcessInstance(String id) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
    }

    @Override
    public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
    }

    @Override
    public String createProcessInstanceById(@Valid BpmProcessInstanceCreateReqVO createReqVO) {
        if (StringUtils.isEmpty(createReqVO.getProcessDefinitionId())){
            throw exception(PROCESS_DEFINITION_ID_IS_EMPTY);
        }
        // 获得流程定义
        ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
        return createProcessInstance0(BpmConstants.TYPE_ID,createReqVO,definition);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createProcessInstanceByKey(@Valid BpmProcessInstanceCreateReqVO createReqVO) {
        if (StringUtils.isEmpty(createReqVO.getProcessDefinitionKey())){
            throw exception(PROCESS_DEFINITION_KEY_IS_EMPTY);
        }
        // 获得流程定义
        ProcessDefinition definition = processDefinitionService.getActiveProcessDefinitionByKey(createReqVO.getProcessDefinitionKey());

        return createProcessInstance0(BpmConstants.TYPE_KEY,createReqVO,definition);
    }


    private String createProcessInstance0(String type, BpmProcessInstanceCreateReqVO createReqVO ,ProcessDefinition definition){
        // 校验流程定义
        Optional.ofNullable(definition).orElseThrow(() -> exception(PROCESS_DEFINITION_NOT_EXISTS));
        if (definition.isSuspended()) {
            throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
        }
        Map<String, Object> variables = Objects.nonNull(createReqVO.getProcessVariables()) ? 
                createReqVO.getProcessVariables() : new HashMap<>();
        String userCode = StringUtils.isEmpty(createReqVO.getProcessStartUserId()) ? SecurityUtils.getUserCode() : createReqVO.getProcessStartUserId();
        //设置全局变量
        //variables.put(BpmConstants.START_USER_NAME, userName);
        //variables.put(BpmConstants.START_USER,"["+ userCode +"]" + userName);
        variables.put(BpmConstants.INITIATOR, userCode);
        variables.put(BpmConstants.BUSINESS_KEY, createReqVO.getBusinessKey());
        if (createReqVO.isAutoComplete()) {
            variables.put(BpmConstants.EXECUTE_CMD, BpmExecuteCmd.START.cmd);
        }else {
            variables.put(BpmConstants.EXECUTE_CMD, BpmExecuteCmd.CREATE.cmd);
        }
        
        // 设置流程名称 如果调用接口有传 则设置
        if (StringUtils.isNotEmpty(createReqVO.getProcessName())) {
            variables.put(BpmConstants.PROCESS_NAME, createReqVO.getProcessName());
        }
        

        ProcessInstance instance;

        //设置流程发起者
        Authentication.setAuthenticatedUserId(userCode);
        
        instance = runtimeService.startProcessInstanceById(definition.getId(), createReqVO.getBusinessKey(), variables);
        String processInstanceId = instance.getProcessInstanceId();
        Optional.ofNullable(createReqVO.getProcessName())
                .ifPresent(name -> runtimeService.setProcessInstanceName(processInstanceId, name));

        /*// 创建流程实例
        if (BpmConstants.TYPE_ID.equals(type)){
            instance = runtimeService.startProcessInstanceById(definition.getId(), createReqVO.getBusinessKey(), variables);
        }else{
            instance = runtimeService.startProcessInstanceByKeyAndTenantId(definition.getKey(), createReqVO.getBusinessKey(), variables, definition.getTenantId());
        }*/

        //这个方法最终使用一个ThreadLocal类型的变量进行存储，也就是与当前的线程绑定，所以流程实例启动完毕之后，需要设置为null，防止多线程的时候出问题。
        Authentication.setAuthenticatedUserId(null);


        return processInstanceId;

    }


    @Override
    public void diagramViewByProInstId(HttpServletResponse httpServletResponse, String processInstanceId) throws Exception {
        // 获得当前活动的节点
        String processDefinitionId;
        // 如果流程已经结束，则得到结束节点
        if (this.isProcessFinished(processInstanceId)) {
            HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        } else {
            // 如果流程没有结束，则取当前活动节点
            // 根据流程实例ID获得当前处于活动状态的ActivityId合集
            ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            processDefinitionId = pi.getProcessDefinitionId();
        }
        List<String> highLightedActivities = new ArrayList<>();

        // 获得活动的节点
        List<HistoricActivityInstance> highLightedActivityList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();
        List<String> highLightedFlows = new ArrayList<>();

        for (HistoricActivityInstance tempActivity : highLightedActivityList) {
            String activityId = tempActivity.getActivityId();
            highLightedActivities.add(activityId);
            if("sequenceFlow".equals(tempActivity.getActivityType())){
                highLightedFlows.add(tempActivity.getActivityId());
            }
        }

        // 获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();

        ProcessDiagramGenerator diagramGenerator = engConf.getProcessDiagramGenerator();
//		ProcessDiagramGenerator diagramGenerator = new CustomProcessDiagramGenerator();
        InputStream in = diagramGenerator.generateDiagram(bpmnModel,
                "png",
                highLightedActivities,
                highLightedFlows,
                engConf.getActivityFontName(),
                engConf.getLabelFontName(),
                engConf.getAnnotationFontName(),
                engConf.getClassLoader(), 1.0, true);
        OutputStream out = null;
        byte[] buf = new byte[1024];
        int length;
        try {
            out = httpServletResponse.getOutputStream();
            while ((length = in.read(buf)) != -1) {
                out.write(buf, 0, length);
            }
        } catch (IOException e) {
            log.error("操作异常", e);
            throw new Exception(e);
        } finally {
            IoUtil.closeSilently(out);
            IoUtil.closeSilently(in);
        }
    }

    @Override
    public boolean isProcessFinished(String processInstanceId) {
        return historyService.createHistoricProcessInstanceQuery().finished()
                .processInstanceId(processInstanceId).count()>0;
    }

    @Override
    public BpmProcessInstanceRespVO getProcessInstanceVO(String id) {
        // 获得流程实例
        HistoricProcessInstance processInstance = getHistoricProcessInstance(id);
        if (processInstance == null) {
            return null;
        }
        return BpmProcessInstanceConvert.INSTANCE.convert(processInstance);
    }

    @Override
    public String getProcessStartUser(String processInstanceId) {
        return historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult()
                .getStartUserId();
    }

    @Override
    public List<ProcessInstance> getProcessInstanceByBusinessKey(String businessKey) {
        return Optional.ofNullable(businessKey)
                .map(k -> runtimeService.createProcessInstanceQuery()
                        .processInstanceBusinessKey(k).list())
                .orElse(Collections.emptyList());
        /*return runtimeService.createProcessInstanceQuery()
                .processInstanceBusinessKey(businessKey).list();*/
    }

    @Override
    public List<HistoricProcessInstance> getHisProcessInstancesByBusinessKey(String businessKey) {
        return Optional.ofNullable(businessKey)
                .map(k ->  historyService.createHistoricProcessInstanceQuery()
                        .processInstanceBusinessKey(k)
                        .orderByProcessInstanceStartTime()
                        .asc().list())
                .orElse(Collections.emptyList());
    }

    @Override
    public HistoricProcessInstance getMainProcessInstanceByBusinessKey(String businessKey) {
        return historyService.createHistoricProcessInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .excludeSubprocesses(true)
                .singleResult();
    }

    @Override
    public HistoricProcessInstance getMainProcessInstanceByProcessInstanceId(String id) {
        HistoricProcessInstance instance = this.getHistoricProcessInstance(id);
        return historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(id)
                .processInstanceBusinessKey(instance.getBusinessKey())
                .excludeSubprocesses(true)
                .singleResult();
    }

    @Override
    public List<BpmProcessInstanceRespVO> getProcessInstancesByBusinessKey(String businessKey) {
        List<HistoricProcessInstance> list = getHisProcessInstancesByBusinessKey(businessKey);
        
        return BpmProcessInstanceConvert.INSTANCE.convertList4(list);
    }

    @Override
    public List<BpmProcessInstanceRespVO> findParentProcesses(BpmBaseDO reqVo) {
        HistoricProcessInstance instance = getHistoricProcessInstance(reqVo.getProcessInstanceId());
        List<BpmProcessInstanceRespVO> dataList = new ArrayList<>();
        if (Objects.nonNull(instance.getSuperProcessInstanceId()) && StringUtils.isNotEmpty(reqVo.getBusinessKey()) ) {
            List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceBusinessKey(reqVo.getBusinessKey())
                    .orderByProcessInstanceStartTime().desc()
                    .list();
            FlowableUtil.findParentProcess(instance, list, dataList);
        }

        return dataList;
    }

    @Override
    public List<BpmProcessInstanceRespVO> getProcessesIncludeSuper(BpmBaseDO reqVo) {
        if (StringUtils.isEmpty(reqVo.getProcessInstanceId())) {
            return Collections.emptyList();
        }
        
        List<BpmProcessInstanceRespVO> dataList = new ArrayList<>();
        List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
                .processInstanceBusinessKey(reqVo.getBusinessKey())
                .orderByProcessInstanceStartTime().desc()
                .list();
        // 当前的实例
        HistoricProcessInstance instance = CollectionUtils.findFirst(list, p -> Objects.equals(p.getId(), reqVo.getProcessInstanceId()));
        Optional.ofNullable(instance).ifPresent(i -> {
            dataList.add(BpmProcessInstanceConvert.INSTANCE.convert(i));
            if (Objects.nonNull(instance.getSuperProcessInstanceId())  ) {
                FlowableUtil.findParentProcess(instance, list, dataList);
            }
        });

        return dataList;
    }

    @Override
    public void updateProcessStartUser(String userId, String processInstanceId) {
        processInstanceMapper.setHiInstStartUser(userId, processInstanceId);
        processInstanceMapper.setRuInstStartUser(userId, processInstanceId);
    }

    @Override
    public void updateHiProcessBusinessKey(String businessKey, String procInstId) {
        processInstanceMapper.setHiInstBusinessKey(businessKey, procInstId);
    }

    @Override
    public void updateProcessNameByInstancesId(String processInstancesId,String newProcessName){
        runtimeService.setProcessInstanceName(processInstancesId,newProcessName);
    }

    @Override
    public List<BpmProcessInstancePageItemRespVO> getDocProcessData(List<String> businessKeys) {
        return processInstanceMapper.getDocProcessData(businessKeys);
    }


}
